// Copyright 2022 AndeyaLee. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
	"fmt"
	"net/http"
)

// ErrorCode represents different types of errors in the framework
type ErrorCode int

const (
	// ErrorCodeInvalidPath represents invalid path configuration
	ErrorCodeInvalidPath ErrorCode = iota + 1000
	// ErrorCodeDuplicateRoute represents duplicate route registration
	ErrorCodeDuplicateRoute
	// ErrorCodeInvalidWildcard represents invalid wildcard configuration
	ErrorCodeInvalidWildcard
	// ErrorCodeInvalidNodeType represents invalid node type
	ErrorCodeInvalidNodeType
	// ErrorCodeInvalidConfig represents invalid configuration
	ErrorCodeInvalidConfig
	// ErrorCodeSessionError represents session-related errors
	ErrorCodeSessionError
	// ErrorCodeDatabaseError represents database-related errors
	ErrorCodeDatabaseError
	// ErrorCodeValidationError represents validation errors
	ErrorCodeValidationError
)

// FaygoError represents a structured error in the framework
type FaygoError struct {
	Code    ErrorCode `json:"code"`
	Message string    `json:"message"`
	Details string    `json:"details,omitempty"`
	Err     error     `json:"-"`
}

// Error implements the error interface
func (e *FaygoError) Error() string {
	if e.Details != "" {
		return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Details)
	}
	return fmt.Sprintf("[%d] %s", e.Code, e.Message)
}

// Unwrap returns the underlying error
func (e *FaygoError) Unwrap() error {
	return e.Err
}

// NewError creates a new FaygoError
func NewError(code ErrorCode, message string) *FaygoError {
	return &FaygoError{
		Code:    code,
		Message: message,
	}
}

// NewErrorWithDetails creates a new FaygoError with details
func NewErrorWithDetails(code ErrorCode, message, details string) *FaygoError {
	return &FaygoError{
		Code:    code,
		Message: message,
		Details: details,
	}
}

// NewErrorWithCause creates a new FaygoError with underlying cause
func NewErrorWithCause(code ErrorCode, message string, err error) *FaygoError {
	return &FaygoError{
		Code:    code,
		Message: message,
		Err:     err,
	}
}

// Error handlers for different error types
var (
	// Path-related errors
	ErrInvalidPath     = NewError(ErrorCodeInvalidPath, "invalid path configuration")
	ErrDuplicateRoute  = NewError(ErrorCodeDuplicateRoute, "duplicate route registration")
	ErrInvalidWildcard = NewError(ErrorCodeInvalidWildcard, "invalid wildcard configuration")
	ErrInvalidNodeType = NewError(ErrorCodeInvalidNodeType, "invalid node type")

	// Configuration errors
	ErrInvalidConfig = NewError(ErrorCodeInvalidConfig, "invalid configuration")

	// Session errors
	ErrSessionError = NewError(ErrorCodeSessionError, "session error")

	// Database errors
	ErrDatabaseError = NewError(ErrorCodeDatabaseError, "database error")

	// Validation errors
	ErrValidationError = NewError(ErrorCodeValidationError, "validation error")
)

// ErrorResponse represents a structured error response
type ErrorResponse struct {
	Error   *FaygoError `json:"error"`
	Request struct {
		Method string `json:"method"`
		Path   string `json:"path"`
	} `json:"request"`
	Timestamp string `json:"timestamp"`
}

// HandleStructuredError handles errors in a consistent way
func HandleStructuredError(ctx *Context, err error) {
	if ctx == nil {
		return
	}

	var faygoErr *FaygoError
	var ok bool

	// Check if it's already a FaygoError
	if faygoErr, ok = err.(*FaygoError); !ok {
		// Wrap unknown errors
		faygoErr = NewErrorWithCause(ErrorCodeValidationError, "internal error", err)
	}

	// Log the error
	ctx.Log().Errorf("Error handling request: %v", faygoErr)

	// Determine HTTP status code based on error type
	statusCode := getStatusCodeFromError(faygoErr)

	// Create error response
	errorResp := ErrorResponse{
		Error: faygoErr,
		Request: struct {
			Method string `json:"method"`
			Path   string `json:"path"`
		}{
			Method: ctx.Method(),
			Path:   ctx.Path(),
		},
		Timestamp: getCurrentTimestamp(),
	}

	// Send error response
	ctx.JSON(statusCode, errorResp)
}

// getStatusCodeFromError maps error codes to HTTP status codes
func getStatusCodeFromError(err *FaygoError) int {
	switch err.Code {
	case ErrorCodeInvalidPath, ErrorCodeInvalidWildcard, ErrorCodeInvalidNodeType:
		return http.StatusBadRequest
	case ErrorCodeDuplicateRoute:
		return http.StatusConflict
	case ErrorCodeInvalidConfig:
		return http.StatusInternalServerError
	case ErrorCodeSessionError:
		return http.StatusUnauthorized
	case ErrorCodeDatabaseError:
		return http.StatusServiceUnavailable
	case ErrorCodeValidationError:
		return http.StatusBadRequest
	default:
		return http.StatusInternalServerError
	}
}

// getCurrentTimestamp returns current timestamp in ISO format
func getCurrentTimestamp() string {
	// This would typically use time.Now().Format(time.RFC3339)
	// For now, return a placeholder
	return "2024-01-01T00:00:00Z"
}

// RecoverMiddleware provides panic recovery middleware
func RecoverMiddleware() HandlerFunc {
	return func(ctx *Context) error {
		defer func() {
			if r := recover(); r != nil {
				// Convert panic to error
				var err error
				switch v := r.(type) {
				case error:
					err = v
				case string:
					err = NewError(ErrorCodeValidationError, v)
				default:
					err = NewError(ErrorCodeValidationError, fmt.Sprintf("panic: %v", v))
				}

				// Handle the error
				HandleStructuredError(ctx, err)
			}
		}()

		return nil
	}
}

// ValidatePath validates a route path
func ValidatePath(path string) error {
	if path == "" {
		return NewErrorWithDetails(ErrorCodeInvalidPath, "path cannot be empty", "")
	}

	if path[0] != '/' {
		return NewErrorWithDetails(ErrorCodeInvalidPath, "path must start with /", path)
	}

	// Check for invalid characters
	for i, c := range path {
		if c == ' ' || c == '\t' || c == '\n' || c == '\r' {
			return NewErrorWithDetails(ErrorCodeInvalidPath, "path contains invalid characters", fmt.Sprintf("position %d: %c", i, c))
		}
	}

	return nil
}

// ValidateWildcard validates wildcard patterns in paths
func ValidateWildcard(path string) error {
	// Check for multiple wildcards in same segment
	colonCount := 0
	starCount := 0

	for i, c := range path {
		if c == ':' {
			colonCount++
			if colonCount > 1 {
				return NewErrorWithDetails(ErrorCodeInvalidWildcard, "only one wildcard per path segment allowed", fmt.Sprintf("position %d", i))
			}
		} else if c == '*' {
			starCount++
			if starCount > 1 {
				return NewErrorWithDetails(ErrorCodeInvalidWildcard, "only one catch-all wildcard allowed", fmt.Sprintf("position %d", i))
			}
		} else if c == '/' {
			// Reset counters for new segment
			colonCount = 0
		}
	}

	return nil
}
